/*
 *  JSpinField.java  - A spin field using a JSpinner (JDK 1.4)
 *  Copyright (C) 2004 Kai Toedter
 *  kai@toedter.com
 *  www.toedter.com
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public License
 *  as published by the Free Software Foundation; either version 2
 *  of the License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */
package com.pkdl.datepicker;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.BorderFactory;
import javax.swing.JPanel;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingConstants;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

/**
 * JSpinField2 is a numeric field with 2 spin buttons to increase or decrease
 * the value. It has the same interface as the "old" JSpinField but uses a
 * JSpinner internally (since J2SE SDK 1.4) rather than a scrollbar for
 * emulating the spin buttons.
 * 
 * @author Kai Toedter
 * @version 1.2
 */
public class JSpinField extends JPanel implements ChangeListener,
		CaretListener, ActionListener {
	protected JSpinner spinner;

	/** the text (number) field */
	protected JTextField textField;
	protected int min;
	protected int max;
	protected int value;
	protected Color darkGreen;

	/**
	 * Default JSpinField constructor.
	 */
	public JSpinField() {
		this(0, Integer.MAX_VALUE);
	}

	/**
	 * JSpinField constructor with given minimum and maximum vaues..
	 */
	public JSpinField(int min, int max) {
		super();
		this.min = min;
		if (max < min) {
			max = min;
		}
		this.max = max;
		this.value = 0;
		if (this.value < min) {
			this.value = min;
		}
		if (this.value > max) {
			this.value = max;
		}

		this.darkGreen = new Color(0, 150, 0);
		this.setLayout(new BorderLayout());
		this.textField = new JTextField();
		this.textField.addCaretListener(this);
		this.textField.addActionListener(this);
		this.textField.setHorizontalAlignment(SwingConstants.RIGHT);
		this.textField.setBorder(BorderFactory.createEmptyBorder());
		this.textField.setText(Integer.toString(this.value));
		this.spinner = new JSpinner();
		this.spinner.setEditor(this.textField);
		this.spinner.addChangeListener(this);
		this.add(this.spinner, BorderLayout.CENTER);
	}

	/**
	 * After any user input, the value of the textfield is proofed. Depending on
	 * being an integer, the value is colored green or red. If the textfield is
	 * green, the enter key is accepted and the new value is set.
	 * 
	 * @param e
	 *            Description of the Parameter
	 */
	public void actionPerformed(ActionEvent e) {
		if (this.textField.getForeground().equals(this.darkGreen)) {
			this.setValue(Integer.valueOf(this.textField.getText()).intValue());
		}
	}

	public void adjustWidthToMaximumValue() {
		JTextField testTextField = new JTextField(Integer.toString(this.max));
		int width = testTextField.getPreferredSize().width;
		int height = testTextField.getPreferredSize().height;
		this.textField.setPreferredSize(new Dimension(width, height));
		this.textField.revalidate();
	}

	/**
	 * After any user input, the value of the textfield is proofed. Depending on
	 * being an integer, the value is colored green or red.
	 * 
	 * @param e
	 *            Description of the Parameter
	 */
	public void caretUpdate(CaretEvent e) {
		try {
			int testValue = Integer.valueOf(this.textField.getText())
					.intValue();

			if ((testValue >= this.min) && (testValue <= this.max)) {
				this.textField.setForeground(this.darkGreen);
				this.setValue(testValue, false, true);
			} else {
				this.textField.setForeground(Color.red);
			}
		} catch (Exception ex) {
			if (ex instanceof NumberFormatException) {
				this.textField.setForeground(Color.red);
			}

			// Ignore all other exceptions, e.g. illegal state exception
		}

		this.textField.repaint();
	}

	/**
	 * Returns the maximum value.
	 * 
	 * @return the maximum value
	 */
	public int getMaximum() {
		return this.max;
	}

	/**
	 * Returns the minimum value.
	 * 
	 * @return the minimum value
	 */
	public int getMinimum() {
		return this.min;
	}

	/**
	 * Returns "JSpinField".
	 * 
	 * @return the name value
	 */
	public String getName() {
		return "JSpinField";
	}

	/**
	 * Returns the year chooser's spinner (which allow the focus to be set to
	 * it).
	 * 
	 * @return Component the spinner or null, if the month chooser has no
	 *         spinner
	 */
	public Component getSpinner() {
		return this.spinner;
	}

	/**
	 * Returns the value.
	 * 
	 * @return the value value
	 */
	public int getValue() {
		return this.value;
	}

	/**
	 * Enable or disable the JSpinField.
	 * 
	 * @param enabled
	 *            The new enabled value
	 */
	public void setEnabled(boolean enabled) {
		super.setEnabled(enabled);
		this.textField.setEnabled(enabled);
		this.spinner.setEnabled(enabled);
	}

	/**
	 * Sets the font property.
	 * 
	 * @param font
	 *            the new font
	 */
	public void setFont(Font font) {
		if (this.textField != null) {
			this.textField.setFont(font);
		}
	}

	/**
	 * DOCUMENT ME!
	 * 
	 * @param fg
	 *            DOCUMENT ME!
	 */
	public void setForeground(Color fg) {
		if (this.textField != null) {
			this.textField.setForeground(fg);
		}
	}

	/**
	 * Sets the horizontal alignment of the displayed value.
	 * 
	 * @param alignment
	 *            the horizontal alignment
	 */
	public void setHorizontalAlignment(int alignment) {
		this.textField.setHorizontalAlignment(alignment);
	}

	/**
	 * Sets the maximum value and adjusts the preferred width.
	 * 
	 * @param newMaximum
	 *            the new maximum value
	 * 
	 * @see #getMaximum
	 */
	public void setMaximum(int newMaximum) {
		this.max = newMaximum;
	}

	/**
	 * Sets the minimum value.
	 * 
	 * @param newMinimum
	 *            the new minimum value
	 * 
	 * @see #getMinimum
	 */
	public void setMinimum(int newMinimum) {
		this.min = newMinimum;
	}

	/**
	 * Sets the value. This is a bound property.
	 * 
	 * @param newValue
	 *            the new value
	 * 
	 * @see #getValue
	 */
	public void setValue(int newValue) {
		this.setValue(newValue, true, true);
		this.spinner.setValue(new Integer(this.value));
	}

	/**
	 * Sets the value attribute of the JSpinField object.
	 * 
	 * @param newValue
	 *            The new value
	 * @param updateTextField
	 *            true if text field should be updated
	 */
	protected void setValue(int newValue, boolean updateTextField,
			boolean firePropertyChange) {
		int oldValue = this.value;

		if (newValue < this.min) {
			this.value = this.min;
		} else if (newValue > this.max) {
			this.value = this.max;
		} else {
			this.value = newValue;
		}

		if (updateTextField) {
			this.textField.setText(Integer.toString(this.value));
			this.textField.setForeground(Color.black);
		}

		if (firePropertyChange) {
			this.firePropertyChange("value", oldValue, this.value);
		}
	}

	/**
	 * Is invoked when the spinner model changes
	 * 
	 * @param e
	 *            the ChangeEvent
	 */
	public void stateChanged(ChangeEvent e) {
		SpinnerNumberModel model = (SpinnerNumberModel) this.spinner.getModel();
		int value = model.getNumber().intValue();
		this.setValue(value);
	}

}